﻿Imports ImageMagick
Imports System.Drawing
Imports System.IO
Imports System.Net
Imports System.Runtime.ExceptionServices
Imports System.Security
Imports System.Text.RegularExpressions
Imports System.Threading

Module Module1
    Public Property Configuration As ConvertSettings

    <HandleProcessCorruptedStateExceptionsAttribute>
    <SecurityCriticalAttribute>
    Sub Main()
        AddHandler AppDomain.CurrentDomain.UnhandledException, AddressOf UnhandledException
        Select Case My.Application.CommandLineArgs.Count
            Case 0 To 4
                PrintHelp()
            Case 5
                Configuration = New ConvertSettings(My.Application.CommandLineArgs)
                If Configuration.ConfigurationOk() Then
                    Try
                        Dim i As Integer = Task.Run(Function() DoImageGeneration(Configuration)).Result
                    Catch aggex As AggregateException
                        Console.WriteLine(GetErrorStr(aggex, "*** Conversion exception ***"))
                    Catch ex As Exception
                        Console.WriteLine(GetErrorStr(ex, "*** Conversion exception ***"))
                    Finally
                        Console.WriteLine("Done")
                    End Try
                End If
            Case Else
                Configuration = New ConvertSettings(My.Application.CommandLineArgs)
                If Configuration.ConfigurationOk() Then
                    Try
                        Dim i As Integer = Task.Run(Function() DoImageGeneration(Configuration)).Result
                    Catch aggex As AggregateException
                        Console.WriteLine(GetErrorStr(aggex, "*** Conversion exception ***"))
                    Catch ex As Exception
                        Console.WriteLine(GetErrorStr(ex, "*** Conversion exception ***"))
                    Finally
                        Console.WriteLine("Done")
                    End Try
                End If
        End Select
    End Sub

    Private Sub OnScriptWrite(sender As Object, arguments As ScriptWriteEventArgs)
        Dim fileName As String = IO.Path.Combine(Configuration.Destination,
                                                 DateTime.Now.ToString("yyyMMdd_HHmmssfff") & "_" &
                                                arguments.Image.Height.ToString() & "x" &
                                                arguments.Image.Width.ToString() & "px " & "." & arguments.Image.Format.ToString())

        arguments.Image.Write(fileName)
    End Sub

    Private Sub UnhandledException(ByVal sender As Object, ByVal e As UnhandledExceptionEventArgs)

        WriteLogMessage(GetErrorStr(DirectCast(e.ExceptionObject, Exception), "Unhandled"))
    End Sub

    Private Sub WriteLogMessage(message As String)
        Console.WriteLine(message)
    End Sub

    Public Sub MagickNetLog(sender As Object, arguments As LogEventArgs)
        ' Write log message
        WriteLogMessage(arguments.Message)
    End Sub

    Public Function GetErrorStr(ByVal ex As Exception, ByVal description As String) As String
        Dim tempException As Exception = ex
        Dim sb As New System.Text.StringBuilder

        sb.Append("----------")
        sb.Append(Environment.NewLine)
        sb.Append(description)
        sb.Append(Environment.NewLine)
        sb.Append("----------")
        sb.Append(Environment.NewLine)

        While Not (tempException Is Nothing)
            If tempException.GetType() Is GetType(WebException) Then
                Dim tempWebexception As WebException = DirectCast(tempException, WebException)
                sb.Append(tempException.GetType().ToString())
                sb.Append(": [")
                sb.Append(tempWebexception.Status)
                sb.Append("]")
                sb.Append(tempException.Message)
                sb.Append(Environment.NewLine)
                sb.Append("----------")
                sb.Append(Environment.NewLine)
            Else
                sb.Append(tempException.GetType().ToString())
                sb.Append(": ")
                sb.Append(tempException.Message)
                sb.Append(Environment.NewLine)
                sb.Append("----------")
                sb.Append(Environment.NewLine)
            End If

            If tempException.Data.Count > 0 Then
                For Each de As DictionaryEntry In tempException.Data
                    sb.AppendFormat("..{0} = {1}", de.Key.ToString(), de.Value)
                Next
                sb.Append("----------")
                sb.Append(Environment.NewLine)
            End If

            sb.Append(tempException.Source)
            sb.Append(Environment.NewLine)
            sb.Append("----------")
            sb.Append(Environment.NewLine)
            sb.Append(tempException.StackTrace)
            sb.Append(Environment.NewLine)
            sb.Append("----------")
            sb.Append(Environment.NewLine)
            tempException = tempException.InnerException
        End While
        Return sb.ToString()
    End Function

    Private Sub PrintHelp()
        Console.WriteLine(" ")
        Console.WriteLine("Usage MgImgGen")
        Console.WriteLine(" ")
        Console.WriteLine("  Generates article images from source images ")
        Console.WriteLine("  and writes them in an output directory")
        Console.WriteLine(" ")
        Console.WriteLine("  MgImgGen.exe ")
        Console.WriteLine("       -sart#[start articleId number]")
        Console.WriteLine("       -src#[path to source images 1920x1080]")
        Console.WriteLine("       -out#[path to output directory]")
        Console.WriteLine("       -n#[number of article images to generate]")
        Console.WriteLine("       -f#[image format for output jpg, png, ...]")
        Console.WriteLine("       optional:")
        Console.WriteLine("       -bdm#[image address enable 1111111111 (default) ")
        Console.WriteLine("             1000010001 only 3 images will get generated")
        Console.WriteLine("              image_1 for BILDDATEI (first position == 1), ")
        Console.WriteLine("              image_6 for BILDDATEI_6 (sixth position == 1) and  ")
        Console.WriteLine("              image_A for BILDDATEI_A (last position == 1) are generated]")
        Console.WriteLine("       -font#[Fontname Arial, Impact, Consolas...]")
        Console.WriteLine("       -sza#[size factor article Id 400]")
        Console.WriteLine("       -szd#[size factor date time 160]")
        Console.WriteLine("       -szn#[size factor address 200]")
        Console.WriteLine("       -al#[max digits for article id number 7]")
        Console.WriteLine(" sample:")
        Console.WriteLine("    MgImgGen.exe -n#100 -src#F:\Img\Src1920x1080 -out#F:\AutoImport\Images -sart#100 -f#png")
        Console.WriteLine(" ")
        Console.WriteLine(" would generate png article images from 0000101_[1-A].png to 0000201_[1-A].png ")
        Console.WriteLine(" from images in F:\Img\Src1920x1080 and")
        Console.WriteLine(" strore them in F:\DataDB\GraphicsService\AutoImport\Images")
        Console.WriteLine(" ")
    End Sub

    Friend Class ConvertSettings
        Private m_imageaddress As String

        Public Function ExportAddress(imageAddress As Integer) As Boolean
            If String.IsNullOrWhiteSpace(m_imageaddress) Then Return True
            If m_imageaddress.Length < imageAddress AndAlso imageAddress > 0 Then Return False
            Try
                Return (m_imageaddress.ToCharArray()(imageAddress - 1) = "1")
            Catch ex As Exception
                Return False
            End Try
        End Function

        Private m_format As String

        Public ReadOnly Property ImageFormat As MagickFormat
            Get
                Select Case m_format.ToLowerInvariant
                    Case "jpg", "jpeg"
                        Return MagickFormat.Jpg
                    Case "pdf"
                        Return MagickFormat.Pdf
                    Case "bmp"
                        Return MagickFormat.Bmp
                    Case "gif"
                        Return MagickFormat.Gif
                    Case "tga"
                        Return MagickFormat.Tga
                    Case "tiff", "tif"
                        Return MagickFormat.Tiff
                    Case "dds"
                        Return MagickFormat.Dds
                    Case Else
                        Return MagickFormat.Png
                End Select
            End Get
        End Property

        Public Property Font As String
            Get
                If String.IsNullOrWhiteSpace(m_font) = False Then
                    Return m_font
                Else
                    Return "Impact"
                End If
            End Get
            Set
                m_font = Value
            End Set
        End Property

        Private ReadOnly m_confOk As Boolean

        Public ReadOnly Property ConfigurationOk As Boolean
            Get
                Return m_confOk
            End Get
        End Property

        Private ReadOnly m_sourcePath As String

        Public ReadOnly Property ImageSource As String
            Get
                Return m_sourcePath
            End Get
        End Property

        Private ReadOnly m_destinationPath As String

        Public ReadOnly Property Destination As String
            Get
                Return m_destinationPath
            End Get
        End Property

        Private ReadOnly m_numberOfArticles As Integer

        ReadOnly Property NumberOfArticles As Integer
            Get

                Return m_numberOfArticles
            End Get
        End Property

        Private ReadOnly m_startArticleNumber As Long
        Private m_font As String

        ReadOnly Property StartArticle As Long
            Get
                Return m_startArticleNumber
            End Get
        End Property

        Private m_fontSizeFactorArticleId As Integer

        ReadOnly Property FontSizeFactorArticleId As Integer
            Get
                Return m_fontSizeFactorArticleId
            End Get
        End Property

        Private m_fontSizeFactorDate As Integer

        ReadOnly Property FontSizeFactorDate As Integer
            Get
                Return m_fontSizeFactorDate
            End Get
        End Property

        Private m_fontSizeFactorAdddress As Integer

        ReadOnly Property FontSizeFactorAdddress As Integer
            Get
                Return m_fontSizeFactorAdddress
            End Get
        End Property

        Private m_maxArticleIdDigits As Integer

        ReadOnly Property maxArticleIdDigits As Integer
            Get
                Return m_maxArticleIdDigits
            End Get
        End Property

        Public Sub New(args As ObjectModel.ReadOnlyCollection(Of String))
            Dim kv As String()
            m_confOk = True
            m_fontSizeFactorArticleId = 400
            m_fontSizeFactorAdddress = 200
            m_fontSizeFactorDate = 160
            m_font = "Impact"
            m_maxArticleIdDigits = 7
            Dim startArticleNumber As String = ""
            For Each argument In args
                kv = Split(argument, "#")
                If kv.Count = 1 Then kv = Split(argument, "=")
                If kv.Count = 2 Then
                    Console.WriteLine(String.Format("{0}:{1}", kv(0), kv(1)))
                    Select Case kv(0).ToLowerInvariant.Trim
                        Case "-f"
                            m_format = kv(1)
                        Case "-n"
                            If Not Integer.TryParse(kv(1), m_numberOfArticles) Then
                                Console.WriteLine(String.Format("*** Invalid number: {0} for number of article images ***", kv(1)))
                                m_confOk = False
                                Exit For
                            End If
                        Case "-sart"
                            startArticleNumber = kv(1)
                            If String.IsNullOrWhiteSpace(startArticleNumber) = False Then
                                Dim strTemp As String = kv(1)
                                If strTemp.Length > m_maxArticleIdDigits Then strTemp = strTemp.Substring(0, m_maxArticleIdDigits)
                                If Not Long.TryParse(strTemp, m_startArticleNumber) Then
                                    Console.WriteLine(String.Format("*** Invalid number: {0} for number of starting article number ***", kv(1)))
                                    m_confOk = False
                                    Exit For
                                End If
                            End If
                        Case "-out"
                            m_destinationPath = kv(1).Trim
                            If Not My.Computer.FileSystem.DirectoryExists(m_destinationPath) Then
                                Console.WriteLine(String.Format("*** destination directory '{0}' doesn't exist ***", kv(1)))
                                m_confOk = False
                                Exit For
                            End If
                        Case "-src"
                            m_sourcePath = kv(1).Trim
                            If Not My.Computer.FileSystem.DirectoryExists(m_sourcePath) Then
                                Console.WriteLine(String.Format("*** image source directory '{0}' doesn't exist ***", kv(1)))
                                m_confOk = False
                                Exit For
                            ElseIf My.Computer.FileSystem.GetFiles(m_sourcePath).Count = 0 Then
                                Console.WriteLine(String.Format("*** no images in image source directory '{0}' ***", kv(1)))
                                m_confOk = False
                                Exit For
                            End If
                        Case "-bdm"
                            m_imageaddress = kv(1).Trim
                        Case "-font"
                            Font = kv(1).Trim
                        Case "-sza"
                            If Not Integer.TryParse(kv(1), m_fontSizeFactorArticleId) Then
                                m_fontSizeFactorArticleId = 400
                            End If
                        Case "-szd"
                            If Not Integer.TryParse(kv(1), m_fontSizeFactorDate) Then
                                m_fontSizeFactorDate = 160
                            End If
                        Case "-szn"
                            If Not Integer.TryParse(kv(1), m_fontSizeFactorAdddress) Then
                                m_fontSizeFactorAdddress = 200
                            End If
                        Case "-al"
                            If Not Integer.TryParse(kv(1), m_maxArticleIdDigits) Then
                                m_maxArticleIdDigits = 7
                            End If
                            Console.WriteLine(String.Format("..articleId: {0} maxDigits: {1}", startArticleNumber, m_maxArticleIdDigits))
                            If String.IsNullOrWhiteSpace(startArticleNumber) = False Then
                                Dim strTemp As String = startArticleNumber
                                If strTemp.Length > m_maxArticleIdDigits Then strTemp = strTemp.Substring(0, m_maxArticleIdDigits)
                                If Not Long.TryParse(strTemp, m_startArticleNumber) Then
                                    Console.WriteLine(String.Format("*** Invalid number: {0} for number of starting article number ***", kv(1)))
                                    m_confOk = False
                                    Exit For
                                End If
                            End If
                        Case Else
                            Console.WriteLine(String.Format("*** Invalid option: {0} ***", kv(0)))
                            m_confOk = False
                            Exit For
                    End Select
                Else
                    Console.WriteLine(String.Format("*** Invalid argument: {0} ***", argument))
                    m_confOk = False
                    Exit For
                End If
            Next
            If Not m_confOk Then PrintHelp()
        End Sub
    End Class

    Private Function DoImageGeneration(conf As ConvertSettings) As Integer

        If My.Computer.FileSystem.DirectoryExists(conf.ImageSource) AndAlso My.Computer.FileSystem.DirectoryExists(conf.Destination) AndAlso conf.NumberOfArticles > 0 AndAlso conf.StartArticle > 0 Then

            ImageMagick.MagickNET.SetLogEvents(LogEvents.None)
            ' Set the log handler (all threads use the same handler)
            AddHandler ImageMagick.MagickNET.Log, AddressOf MagickNetLog
            Dim articleIdnumberFormat = "D" & conf.maxArticleIdDigits.ToString()
            Dim files As ObjectModel.ReadOnlyCollection(Of String)
            files = My.Computer.FileSystem.GetFiles(conf.ImageSource, FileIO.SearchOption.SearchTopLevelOnly, {"*.jpg", "*.png", "*.tif"})
            Dim rnd As New Random()

            Dim fileIndx As Integer = 0
            If files.Count > 0 Then
                Dim endCount As Integer = (conf.StartArticle + conf.NumberOfArticles - 1)

                Parallel.For(CType(conf.StartArticle, Integer), endCount + 1, Sub(index As Integer)
                                                                                  Console.WriteLine(String.Format("{0} ..processing {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), index.ToString(articleIdnumberFormat)))
                                                                                  Dim i As Integer = GenerateArticleImages(files(rnd.Next(files.Count)), index.ToString(articleIdnumberFormat), conf)
                                                                              End Sub)

                Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") & " ..All Done.")
            Else
                Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") & " ..No files All Done.")
            End If
            RemoveHandler ImageMagick.MagickNET.Log, AddressOf MagickNetLog
            Return 0
        Else
            Return 1
        End If
    End Function

    <HandleProcessCorruptedStateExceptionsAttribute>
    <SecurityCriticalAttribute>
    Private Sub MyResize(ByRef img As MagickImage, x As Integer, y As Integer)
        Try
            img.Resize(x, y)
        Catch sex As SystemException
            Console.WriteLine("*** script exception: System")
        Catch ex As Exception
            Console.WriteLine("*** script exception: " & ex.Message)
        Finally
            Console.WriteLine("..resized")
        End Try
    End Sub

    Private Function GenerateArticleImages(srcImage As String, ArtID As String, conf As ConvertSettings) As Integer

        If My.Computer.FileSystem.FileExists(srcImage) AndAlso My.Computer.FileSystem.DirectoryExists(conf.Destination) Then
            Using image As New MagickImage(srcImage)

                If image.Width <> 1920 Then
                    MyResize(image, 1920, 1920)
                End If

                Dim ext As String = conf.ImageFormat.ToString.ToLowerInvariant

                Dim script As New MagickScript("effects.xml")
                Try
                    AddHandler script.Write, AddressOf OnScriptWrite
                    script.Execute(image)
                Catch mnex As MagickImageErrorException
                    Console.WriteLine("*** script exception: " & mnex.Message)
                Catch ex As Exception
                    Console.WriteLine("*** script exception: " & ex.Message)
                Finally
                    Console.WriteLine("..script executed")
                End Try

                Dim resWidth As Integer = image.Width

                Dim imgContainer As New Drawables()
                With imgContainer
                    .Font(conf.Font)
                    .FontPointSize(CInt(conf.FontSizeFactorArticleId * resWidth / 1920))
                    .Gravity(Gravity.East)
                    .StrokeColor(Color.Black)
                    .StrokeWidth(CInt(32 * resWidth / 1920))
                    .FillColor(Color.Black)
                    .Text(0, 0, ArtID)
                End With
                image.Draw(imgContainer)
                With imgContainer
                    .StrokeColor(Color.White)
                    .StrokeWidth(CInt(8 * resWidth / 1920))
                    .FillColor(Color.Gold)
                    .Text(0, 0, ArtID)
                End With
                image.Draw(imgContainer)

                With imgContainer
                    .Font(conf.Font)
                    .FontPointSize(CInt(conf.FontSizeFactorDate * resWidth / 1920))
                    .Gravity(Gravity.Southeast)
                    .StrokeColor(Color.Black)
                    .StrokeWidth(CInt(16 * resWidth / 1920))
                    .FillColor(Color.Black)
                    .Text(0, 0, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))
                End With
                image.Draw(imgContainer)
                With imgContainer
                    .StrokeColor(Color.White)
                    .StrokeWidth(CInt(4 * resWidth / 1920))
                    .FillColor(Color.Gold)
                    .Text(0, 0, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))
                End With
                image.Draw(imgContainer)

                If conf.ImageFormat = MagickFormat.Jpg Then
                    image.ColorFuzz = New Percentage(20)
                    image.Opaque(Color.Transparent, Color.White)
                    image.BackgroundColor = Color.White
                End If

                If conf.ExportAddress(1) Then
                    ExportResizedImage(image, 0, 0, 1, ArtID & "_1." & ext, conf.Destination, conf.Font, conf.FontSizeFactorAdddress)
                End If

                If conf.ExportAddress(2) Then
                    ExportResizedImage(image, 1680, 1680, 2, ArtID & "_2." & ext, conf.Destination, conf.Font, conf.FontSizeFactorAdddress)
                End If

                If conf.ExportAddress(3) Then
                    ExportResizedImage(image, 1440, 1440, 3, ArtID & "_3." & ext, conf.Destination, conf.Font, conf.FontSizeFactorAdddress)
                End If

                If conf.ExportAddress(4) Then
                    ExportResizedImage(image, 1200, 1200, 4, ArtID & "_4." & ext, conf.Destination, conf.Font, conf.FontSizeFactorAdddress)
                End If

                If conf.ExportAddress(5) Then
                    ExportResizedImage(image, 960, 960, 5, ArtID & "_5." & ext, conf.Destination, conf.Font, conf.FontSizeFactorAdddress)
                End If

                If conf.ExportAddress(6) Then
                    ExportResizedImage(image, 720, 720, 6, ArtID & "_6." & ext, conf.Destination, conf.Font, conf.FontSizeFactorAdddress)
                End If

                If conf.ExportAddress(7) Then
                    ExportResizedImage(image, 480, 480, 7, ArtID & "_7." & ext, conf.Destination, conf.Font, conf.FontSizeFactorAdddress)
                End If

                If conf.ExportAddress(8) Then
                    ExportResizedImage(image, 240, 240, 8, ArtID & "_8." & ext, conf.Destination, conf.Font, conf.FontSizeFactorAdddress)
                End If

                If conf.ExportAddress(9) Then
                    ExportResizedImage(image, 240, 240, 9, ArtID & "_9." & ext, conf.Destination, conf.Font, conf.FontSizeFactorAdddress)
                End If

                If conf.ExportAddress(10) Then
                    ExportResizedImage(image, 240, 240, 10, ArtID & "_A." & ext, conf.Destination, conf.Font, conf.FontSizeFactorAdddress)
                End If
            End Using

            Return 0
        Else
            Return 1
        End If
    End Function

    Private Sub ExportResizedImage(img As MagickImage, rsWidth As Integer, rsHeight As Integer, imgAddress As Integer, fileName As String, dest As String, font As String, fontFactor As Integer)

        Dim clone As MagickImage = img.Clone
        If rsHeight > 0 AndAlso rsWidth > 0 Then
            clone.Resize(rsWidth, rsHeight)
        End If

        Using clone
            Dim resWidth As Integer = clone.Width
            'Console.WriteLine(String.Format("..ExportResizedImage(img:{0}, {1}x{2}, addr:{3}, font:{4}, ff:{5},resize ({6}x{7})", fileName, rsWidth, rsHeight, imgAddress, font, fontFactor, clone.Width, clone.Height))
            'Console.WriteLine(String.Format("....PointSize:{0}", CInt(fontFactor * resWidth / 1920)))
            Dim imgContainer As New Drawables()
            With imgContainer
                .Font(font)
                .FontPointSize(CInt(fontFactor * resWidth / 1920))
                .Gravity(Gravity.Northeast)
                .StrokeColor(Color.Black)
                .StrokeWidth(CInt(32 * resWidth / 1920))
                .FillColor(Color.Black)
                .Text(0, 0, imgAddress.ToString)
            End With
            clone.Draw(imgContainer)
            With imgContainer
                .StrokeColor(Color.White)
                .StrokeWidth(CInt(8 * resWidth / 1920))
                .FillColor(Color.Gold)
                .Text(0, 0, imgAddress.ToString)
            End With
            clone.Draw(imgContainer)

            clone.Write(Path.Combine(dest, fileName))
            Console.WriteLine(String.Format("{0} ..  image {1} resize ({2}x{3}) done", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), fileName, clone.Width, clone.Height))
        End Using
    End Sub

    <AttributeUsageAttribute(AttributeTargets.Method, AllowMultiple:=False, Inherited:=False)>
    Public NotInheritable Class HandleProcessCorruptedStateExceptionsAttribute
        Inherits Attribute
    End Class
End Module
